/*
	WaZm Virtual Machine by sirdarckcat@gmail.com
		http://code.google.com/p/wzvm

 Copyright 2007 sirdarckcat@gmail.com
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License. 
 You may obtain a copy of the License at 
 
 http://www.apache.org/licenses/LICENSE-2.0 
 
 Unless required by applicable law or agreed to in writing, software 
 distributed under the License is distributed on an "AS IS" BASIS, 
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 See the License for the specific language governing permissions and 
 limitations under the License.
*/
function WZVM(){
	this.showwarnings=true;
	this.warnings=Array();
	this.MAXINS=999;
	this.debug=false;
	this.int=Array(10);
	this.bp=Array();
	
	this.Z_memory=Array(1000);
	this.W_memory=Array(10000);
	this.W_top=0;
	this.recursion=Array();
	this.recursion.push(1000);
	this.ex=Array(0,0,0,0,0,0,0,0,0,0);
	this.sex=Array(0,0,0,0,0,0,0,0,0,0);
	
	var warn=function(t,e,r){
	/*
		This function will show the warnings.
	*/
		if(this.showwarnings){
			switch(t){
				case 0:
					if(e<r){
						this.warnings.push("Warning: Wrong parameter count.");
					}
				break;
			}
		}
	}
	
	this.tranz=function(zyte){
	/*
		This function will translate from OPCode to Wazm syntax
	*/
		zyte=Math.floor(zyte);
		if(zyte<0)zyte=-zyte;
		zyte%=1000;
		if (zyte<2){
			if (zyte){
				return "ret";
			}else{
				return "ext";
			}
		}
		if (zyte<10){
			return "int "+zyte;
		}
		if (zyte<100){
			var ins=Array("","jmp","call","push","pop","inc","dec","clr","save","load");
			return ins[Math.floor(zyte/10)]+" e"+(zyte%10)+"x";
		}
		var ins=Array("","mov","sum","mul","mov","sum","res","mov","mov","jnz");
		return ""+
		ins[Math.floor(zyte/100)]+" "+((Math.floor(zyte/100)==8)?"[":"")+"e"+Math.floor((zyte%100)/10)+
		"x"+
		((Math.floor(zyte/100)==8)?"]":"")+
		" "+
		((Math.floor(zyte/100)==7)?"[":"")+
		((Math.floor(zyte/100)>3)?"e":"")+
		(zyte%10)+
		((Math.floor(zyte/100)>3)?"x":"")+
		((Math.floor(zyte/100)==7)?"]":"");
	}
	
	this.inter=function(line){
	/*
		This function will transform the Wazm syntax to WZVM OPCodes.
	*/
		try{
			line=line.toString();
			line=line.replace(/>[^>]*>/g," ").replace(/>.*$/g,"").replace(/<.*$/g,"").replace(/ +/g," ").replace(/ +$/,"").replace(/^ +/g,"");
			comd=line.match(/^([a-z]+)( \[?e?[0-9]x?\]?)?( \[?e?[0-9]x?\]?)?$/i);
			if(comd==null)return "Error: Syntax error";
			switch(comd[1]){
				case "ext":
					warn(0,0,comd.length-2);
					return 0;
				break;
				case "ret":
					warn(0,0,comd.length-2);
					return 1;
				break;
				case "int":
					warn(0,1,comd.length-2);
					return Math.floor(comd[2]*1);
				break;
				case "jmp":
					var mat=line.match(/jmp e([0-9])x/i);
					warn(0,1,comd.length-2);
					return 10+(Math.floor(mat[1]*1)%10);
				break;
				case "call":
					var mat=line.match(/call e([0-9])x/i);
					warn(0,1,comd.length-2);
					return 20+(Math.floor(mat[1]*1)%10);
				break;
				case "push":
					var mat=line.match(/push e([0-9])x/i);
					warn(0,1,comd.length-2);
					return 30+(Math.floor(mat[1]*1)%10);
				break;
				case "pop":
					var mat=line.match(/pop e([0-9])x/i);
					warn(0,1,comd.length-2);
					return 40+(Math.floor(mat[1]*1)%10);
				break;
				case "inc":
					var mat=line.match(/inc e([0-9])x/i);
					warn(0,1,comd.length-2);
					return 50+(Math.floor(mat[1]*1)%10);
				break;
				case "dec":
					var mat=line.match(/dec e([0-9])x/i);
					warn(0,1,comd.length-2);
					return 60+(Math.floor(mat[1]*1)%10);
				break;
				case "clr":
					var mat=line.match(/clr e([0-9])x/i);
					warn(0,1,comd.length-2);
					return 70+(Math.floor(mat[1]*1)%10);
				break;
				case "save":
					var mat=line.match(/save e([0-9])x/i);
					warn(0,1,comd.length-2);
					return 80+(Math.floor(mat[1]*1)%10);
				break;
				case "load":
					var mat=line.match(/load e([0-9])x/i);
					warn(0,1,comd.length-2);
					return 90+(Math.floor(mat[1]*1)%10);
				break;
				case "mov":
					if (mat=line.match(/mov e([0-9])x ([0-9])/i)){
						return 100+mat[1]*10+mat[2]*1;
					}else
					if (mat=line.match(/mov e([0-9])x e([0-9])x/i)){
						return 400+mat[1]*10+mat[2]*1;
					}else
					if (mat=line.match(/mov e([0-9])x \[e([0-9])x\]/i)){
						return 700+mat[1]*10+mat[2]*1;
					}else
					if (mat=line.match(/mov \[e([0-9])x\] e([0-9])x/i)){
						return 800+mat[1]*10+mat[2]*1;
					}
					return "Error: Syntax error";
				break;
				case "sum":
					if (mat=line.match(/sum e([0-9])x ([0-9])/i)){
						return 200+mat[1]*10+mat[2]*1;
					}else
					if (mat=line.match(/sum e([0-9])x e([0-9])x/i)){
						return 500+mat[1]*10+mat[2]*1;
					}
					return "Error: Syntax error";
				break;
				case "mul":
					var mat=line.match(/mul e([0-9])x ([0-9])/i);
					if(mat==null || mat.length<3)return "Error: Syntax error";
					var e1=Math.floor(mat[1]*1);
					var e2=Math.floor(mat[2]*1);
					return 300+e1*10+e2;
				break;
				case "res":
					var mat=line.match(/res e([0-9])x e([0-9])x/i);
					if(mat==null || mat.length<3)return "Error: Syntax error";
					var e1=Math.floor(mat[1]*1);
					var e2=Math.floor(mat[2]*1);
					return 600+e1*10+e2;
				break;
				case "jnz":
					var mat=line.match(/jnz e([0-9])x e([0-9])x/i);
					if(mat==null || mat.length<3)return "Error: Syntax error";
					var e1=Math.floor(mat[1]*1);
					var e2=Math.floor(mat[2]*1);
					return 900+e1*10+e2;
				break;
				default:
					return "Error: Command "+comd[1]+" not found";
				break;
			}
		}catch(e){
			return "Error: Syntax error";
		}
	}
	
	this.compile=function (script,save){
	/*
		This function will transform a complete script from Wazm syntax to WZVM OPCodes
	*/
		script=script.replace(/>[^\r\n>]*>/g," ").replace(/>[\r\n]+/g,">").replace(/>[^>]*>/g,"\n").replace(/<.*/g,"").replace(/[\r\n]+/g,"\n").replace(/[\t]+/g,"");
		var lines=script.split(/[\r\n]+/g);
		var zmem=Array();
		for (var i=0;i<lines.length;i++){
			if(lines[i]=="")continue;
			var line=this.inter(lines[i]);
			if(line*1!=line)return line+" at line "+(i);
			zmem.push(line);
		}
		if(save)this.Z_memory=this.Z_memory.concat(zmem);
		return zmem;
	}
	
	this.execline=function (opcode){
	/*
		This function will execute a single instruction of OPCode
	*/
		opcode=Math.abs(Math.floor(opcode))%1000;
		if (opcode==0){
			//ext
			this.ex[0]=10000;
			return;
		}
		if (opcode==1){
			//ret
			this.ex[0]=this.recursion.pop();
			return;
		}
		if (opcode<10){
			//int
			if(this.int[opcode]){
				try{
					x=this.int[opcode];x();
				}catch(e){}
			}
			return;
		}
		if(opcode==10){
			for(i=0;i<100;i++);//rudimentary sleep
			return;
		}
		if (opcode<100){
			var r=this.ex[opcode%10];
			if(opcode%10==0)return;
			switch(Math.floor(opcode/10)){
				case 1:
					//jmp
					r=Math.abs(r);
					this.ex[0]=r;
				break;
				case 2:
					//call
					r=Math.abs(r);
					this.recursion.push(this.ex[0]+1);
					this.ex[0]=r;
				break;
				case 3:
					//push
					this.W_memory[this.W_top++]=r;
				break;
				case 4:
					//pop
					this.ex[opcode%10]=Math.floor(this.W_memory[-1+this.W_top--]);
				break;
				case 5:
					//inc
					this.ex[opcode%10]=(r+1)%10000;
				break;
				case 6:
					//dec
					this.ex[opcode%10]=(r-1)%10000;
				break;
				case 7:
					//clr
					this.ex[opcode%10]=0;
				break;
				case 8:
					//save
					this.sex[opcode%10]=r*1;
				break;
				case 9:
					//load
					this.ex[opcode%10]=this.sex[opcode%10]*1;
				break;
			}
			return;
		}
		var r1=Math.floor((opcode%100)/10);
		var r2=opcode%10;
		if (Math.floor((opcode%100)/10)==0 && opcode<80)return;
		switch(Math.floor(opcode/100)){
			case 1:
				//mov eNx CONST
				this.ex[r1]=r2;
			break;
			case 2:
				//sum eNx CONST
				this.ex[r1]=(this.ex[r1]+r2)%10000;
			break;
			case 3:
				//mul eNx CONST
				this.ex[r1]=(this.ex[r1]*r2)%10000;
			break;
			case 4:
				//mov eNx eMx
				this.ex[r1]=this.ex[r2];
			break;
			case 5:
				//sum eNx eMx
				this.ex[r1]=(this.ex[r1]+this.ex[r2])%10000;
			break;
			case 6:
				//res eNx eMx
				this.ex[r1]=(this.ex[r1]-this.ex[r2])%10000;
			break;
			case 7:
				//mov eNx [eMx]
				this.ex[r1]=this.W_memory[Math.abs(this.ex[r2])];
			break;
			case 8:
				//mov [eNx] eMx
				this.W_memory[Math.abs(this.ex[r1])]=this.ex[r2];
			break;
			case 9:
				//jnz eNx eMx
				if(this.ex[r1]){
					this.ex[0]=Math.abs(this.ex[r2]);
				}
			break;
		}
	}
	
	this.eval=function (code,save){
	/*
		This function will compile and execute a Wazm script into the WZVM.
	*/
		var vim=this.compile(code,save);
		if(!vim.push)return vim;
		this.ex[0]=0;
		var c=0;
		while(this.ex[0]<10000 && vim[this.ex[0]] && c<this.MAXINS){
			c++;
			if(this.debug || this.bp[this.ex[0]])print(this.ex[0]+"("+c+") - "+vim[this.ex[0]]+" => "+this.tranz(vim[this.ex[0]]));
			var prev0=this.ex[0];
			this.execline(vim[this.ex[0]]);
			if(prev0==this.ex[0]){
				this.ex[0]++;
			}
			if(this.debug){
				print(this.ex.toString());
				while(ts=this.warnings.pop()){
					print(ts);
				}
				print("\n");
			}
		}
		return 0;
	}
}